﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using vJine.Core.IoC;
using vJine.Core.IoC.Config;

namespace vJine.Core {
    public class Joint {
        protected Joint() {
        }

        public Type Parent { get; private set; }
        public string Target { get; private set; }
        public string FullName { get; private set; }

        protected void Set(Type Parent, string Target) {
            this.Parent = Parent;
            this.Target = Target;
            this.FullName = this.Target + "@" + Parent.FullName;
        }

        protected InvokeSignature invoker { get; set; }

        public bool HasContext { get; protected set; }

        public ParamAttribute[] Params { get; protected set; }

        public ReturnAttribute[] Return { get; protected set; }

        #region Invoke

        protected virtual ParamAttribute get_param(ParameterInfo param) {
            ParamAttribute[] Params = Reflect.GetAttribute<ParamAttribute>(param);
            if(Params.Length == 0) {
                return new ParamAttribute(param.Name) { Class = param.ParameterType };
            } else {
                Params[0].Class = param.ParameterType;
                return Params[0];
            }
        }

        protected void InitParams(MethodBase M) {
            ParameterInfo[] P = M.GetParameters();
            ParamAttribute[] Params = new ParamAttribute[P.Length];
            for (int i = 0, len = Params.Length; i < len; i++) {
                Params[i] = this.get_param(P[i]);
            }

            this.Params = Params;
        }

        object[] GetParams(object paramContext, object returnContext) {

            Type Tcontext = paramContext.GetType();
            Type Treturn = returnContext.GetType();

            int len_params = this.Params.Length;
            object[] objParams = new object[len_params];
            for (int i = 0; i < len_params; i++) {
                ParamAttribute p_i = this.Params[i];
                Type Tparam = p_i.Class;
                if (Tparam == Tcontext) {
                    objParams[i] = paramContext;
                } else if (Tparam == Treturn) {
                    objParams[i] = returnContext;
                } else {
                    objParams[i] = p_i.Get(paramContext);
                }
            }

            return objParams;
        }

        public object Invoke(object objContext, params object[] Params) {

            if(Params == null && this.Params.Length != 0 ||
                Params != null && Params.Length != this.Params.Length) {
                throw new CoreException("Param Count Miss-Match");
            }

            for (int i = 0, len = Params == null ? 0 : Params.Length; i < len; i++) {
                object objParam = Params[i];
                Type TParam = this.Params[i].Class;
                if (TParam.IsByRef) {
                    TParam = TParam.GetElementType();
                }

                if (objParam == null || objParam.GetType() != TParam) {
                    Params[i] = Class.Parse(objParam, TParam);
                }
            }

            object objReturn = null;
            try {
                if(this.Befores != null && this.Befores.Count > 0) {
                    for(int i = 0, len = this.Befores.Count; i < len; i++) {
                        this.Befores[i].Invoke(objContext, Params);
                    }
                }

                objReturn = this.invoker(objContext, Params);

                if(this.Afters != null && this.Afters.Count > 0) {
                    for(int i = 0, len = this.Afters.Count; i < len; i++) {
                        this.Afters[i].Invoke(objContext, Params);
                    }
                }
            } catch(Exception ex) {
                if(this.Throwns != null && this.Throwns.Count > 0) {
                    for(int i = 0, len = this.Throwns.Count; i < len; i++) {
                        this.Throwns[i].Invoke(objContext, Params); //TODO:传入Exception
                    }
                } else {
                    throw ex;
                }
            }
            return objReturn;
        }

        public object Invoke(object objContext, object paramContext, object returnContext) {
            object objReturn =
                this.Invoke(objContext, this.GetParams(paramContext, returnContext));

            this.SetReturn(objReturn, returnContext);

            return objReturn;
        }

        void SetReturn(object objReturn, object objContext) {
            for (int i = 0, len = this.Return.Length; i < len; i++) {
                ReturnAttribute r = this.Return[i];
                object objR = null;
                if (!string.IsNullOrEmpty(r.Return)) {
                    objR = Class.get(objReturn, r.Return);
                } else if (r.value != null) {
                    objR = r.value;
                } else if (r.CheckPattern()) {
                    string V = objReturn.ToString();
                    for (int j = 0, lenPattern = r.Patterns.Length; j < lenPattern; j++) {
                        if (Regex.IsMatch(V, r.Patterns[i])) {
                            objR = V; break;
                        }
                    }
                } else {
                    objR = objReturn;
                }

                Property.Set(objContext, r.Name, objR);
            }
        }
        #endregion Invoke

        #region AOP

        List<Joint> _Befores = null;
        public List<Joint> Befores {
            get {
                if(this._Befores == null) {
                    this._Befores = new List<Joint>();
                }

                return this._Befores;
            }
            set {
                if(this._Befores != value) {
                    this._Befores = value;
                }
            }
        }

        List<Joint> _Afters = null;
        public List<Joint> Afters {
            get {
                if(this._Afters == null) {
                    this._Afters = new List<Joint>();
                }

                return this._Afters;
            }
            set {
                if(value != this._Afters) {
                    this._Afters = value;
                }
            }
        }

        List<Joint> _Throwns;
        public List<Joint> Throwns {
            get {
                if(this._Throwns == null) {
                    this._Throwns = new List<Joint>();
                }

                return this._Throwns;
            }
            set {
                if(value != this._Throwns) {
                    this._Throwns = value;
                }
            }
        }
        #endregion AOP
    }

    public class InitJoint : Joint {
        public InitJoint(ConstructorInfo Ctor) {
            Type Tobj = Ctor.DeclaringType;
            this.invoker =
                Emit.GenCtor(Tobj, Reflect.GetTypes(Ctor.GetParameters()));

            this.InitParams(Ctor);

            this.Set(Tobj, "Ctor");
        }

        public InitJoint(Type Tobj, params Type[] Tparams) {
            Tparams = Tparams ?? Type.EmptyTypes;

            InvokeSignature _invoker = null; Exception initEx = null;
            try {
                _invoker = Emit.GenCtor(Tobj, Tparams);

                this.InitParams(Tobj.GetConstructor(Tparams));

                this.Set(Tobj, "Ctor");
            } catch (Exception ex) {
                initEx = ex;
            } finally {
                this.invoker = (object objContext, object[] Params) => {
                    if (initEx != null) {
                        throw initEx;
                    }

                    return _invoker(objContext, Params);
                };
            }
        }

        public InitJoint(Type Tobj, Type[] Tparams, Type[] Tgenerics)
            : this(Tobj.MakeGenericType(Tgenerics??Type.EmptyTypes), Tparams??Type.EmptyTypes) {
        }
    }

    public class InitJoint<T> : InitJoint {
        public InitJoint(params Type[] Tparams)
            : base(typeof(T), Tparams??Type.EmptyTypes) {
        }

        public T Invoke(params object[] Params) {
            return (T)base.Invoke(null, Params);
        }
    }

    public class PropertyJoint : Joint {
        protected PropertyJoint(PropertyInfo property, bool IsSetter)
            : this(property.DeclaringType, property.Name, IsSetter) {
        }

        protected PropertyJoint(Type Tobj, string Name, bool IsSetter) {
            try {
                if (IsSetter) {
                    this.invoker = Emit.GenSetter(Tobj, Name);
                } else {
                    this.invoker = Emit.GenGetter(Tobj, Name);
                }

                PropertyInfo p = Reflect.GetProperty(Tobj, Name);
                MethodInfo mInvoke =
                    IsSetter ? p.GetSetMethod(true) : p.GetGetMethod(true);

                this.InitParams(mInvoke);

                this.HasContext = !mInvoke.IsStatic;

                this.Set(Tobj, Name);
            } catch (Exception ex) {
                throw new CoreException(ex, "Error On Joint.Ctor[{0}]@[{1}]", Name, Tobj.FullName);
            }
        }
    }

    public class SetJoint : PropertyJoint {
        public SetJoint(PropertyInfo property)
            : base(property, false) {
        }

        public SetJoint(Type tObj, string Name)
            : base(tObj, Name, true) {
        }
    }

    public class SetJoint<T> : SetJoint {
        public SetJoint(string Name)
            : base(typeof(T), Name) {
        }
    }

    public class GetJoint : PropertyJoint {
        public GetJoint(PropertyInfo property)
            : base(property, false) {
        }

        public GetJoint(Type tObj, string Name)
            : base(tObj, Name, false) {
        }
    }

    public class GetJoint<T> : GetJoint {
        public GetJoint(string Name)
            : base(typeof(T), Name) {

        }
    }

    public class MethodJoint : Joint {

        public MethodJoint(MethodInfo mInvoke)
            : this(mInvoke.DeclaringType, mInvoke.Name, Reflect.GetTypes(mInvoke)) {

        }

        public MethodJoint(Type Tobj, string Name, params Type[] Tparams)
            : this(Tobj, Type.EmptyTypes, Name, Tparams) {
            
        }

        public MethodJoint(Type Tobj, Type[] Tgenerics, string Name, params Type[] Tparams) {
            try {
                MethodInfo mInvoke = Reflect.GetMethod(Tobj, Name, Tparams ?? Type.EmptyTypes);
                {
                    this.InitParams(mInvoke);
                    this.Return =
                        mInvoke.GetCustomAttributes(typeof(ReturnAttribute), true) as ReturnAttribute[];
                }

                this.invoker = Emit.GenCaller(Tobj, Tgenerics, Name, Tparams);

                this.HasContext = !this.invoker.Method.IsStatic;

                this.Set(Tobj, Name);
            } catch(Exception ex) {
                this.invoker = new InvokeSignature((object objContext, object[] Params) => {
                    throw ex;
                });
            }
        }
    }

    public class MethodJoint<T> : MethodJoint {
        public MethodJoint(string Name, params Type[] Tparams)
            : base(typeof(T), Name, Tparams) {

        }

        public MethodJoint(Type[] Tgenerics, string Name, params Type[] Tparams)
            : base(typeof(T), Tgenerics, Name, Tparams) {

        }
    }
}
